/*
 * 代号：凤凰
 * http://www.jphenix.org
 * 2014-06-13
 * V4.0
 */
package com.jphenix.webserver.api;

//#region 【引用区】
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.io.Writer;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.Map;
import java.util.Vector;

import com.jphenix.servlet.filter.BaseFilter;
import com.jphenix.share.tools.Base64;
import com.jphenix.standard.docs.ClassInfo;
import com.jphenix.standard.servlet.api.IHttpSession;
import com.jphenix.standard.servlet.api.IHttpSessionContext;
import com.jphenix.standard.servlet.api.IServletContext;
import com.jphenix.webserver.instancea.ServeConnection;
//#endregion

//#region 【说明区】
/**
 * 会话处理类
 * 
 * 2019-09-17 整理了代码格式
 * 2022-09-04 重构了内置Servlet容器，不依赖外部ServletApi包
 * 
 * @author 刘虻
 * 2006-9-15上午12:10:31
 */
//#endregion
@SuppressWarnings("deprecation")
@ClassInfo({"2022-09-04 21:58","会话处理类"})
public class SessionImpl extends Hashtable<String,Object> implements IHttpSession {
	
	//#region 【声明区】
	// 版本标识
	private static final long serialVersionUID = 5005301247469044903L;

	private           long                createTime;       // 建立时间
	private           long                lastAccessTime;   // 最后访问时间
	private           String              id;               // 会话主键
	private           int                 inactiveInterval; // 间隔时间
	private           boolean             expired;          // 是否会话超时
	
	private transient IServletContext     servletContext;   // Servlet上下文
	private transient IHttpSessionContext sessionContext;   // 会话上下文
	private           ServeConnection     serveConn;        // 交互通信类 
	private           BaseFilter          baseFilter;       // 主过滤器
	//#endregion
	
	//#region 构造函数
	/**
	 * 构造函数
	 * 2008-7-8下午06:21:49
	 */
	public SessionImpl(
			String               id
			,IServletContext     servletContext
			,IHttpSessionContext sessionContext
			,ServeConnection     servConn
			,BaseFilter          baseFilter) {
		this(id, 0, servletContext, sessionContext, servConn, baseFilter);
	}
	//#endregion

	//#region 构造函数
	/**
	 * 构造函数
	 * 2008-7-8下午06:21:53
	 */
	public SessionImpl(
			  String              id
			, int                 inactiveInterval
			, IServletContext     servletContext
			, IHttpSessionContext sessionContext
			, ServeConnection     serveConn
			, BaseFilter          baseFilter) {
		createTime            = System.currentTimeMillis();
		this.id               = id;
		this.servletContext   = servletContext;
		this.sessionContext   = sessionContext;
		this.serveConn        = serveConn;
		this.baseFilter       = baseFilter;
		
		if(baseFilter.isTokenMode()) {
			// token模式不需要会话去检验超时
			this.inactiveInterval = 0;
		}else {
			this.inactiveInterval = inactiveInterval;
		}
	}
	//#endregion

	//#region getCreationTime()
	/**
	 * 覆盖方法
	 * @author 刘虻
	 * 2008-7-8下午06:22:22
	 */
	@Override
    public long getCreationTime() {
		return createTime;
	}
	//#endregion

	//#region getId()
	/**
	 * 覆盖方法
	 * @author 刘虻
	 * 2008-7-8下午06:22:27
	 */
	@Override
    public String getId() {
		if(baseFilter!=null && baseFilter.isTokenMode()) {
			// token 的 key
			String tokenVal = serveConn.getHeader(baseFilter.getTokenKey());
			if(tokenVal==null || tokenVal.length()<1) {
				tokenVal = (String)serveConn.getResponseHeader(baseFilter.getTokenKey());
				if(tokenVal==null || tokenVal.length()<1) {
					tokenVal = baseFilter.generateTokenId();
					serveConn.setHeader(baseFilter.getTokenKey(),tokenVal);
				}
			}
			return tokenVal;
		}
		return id;
	}
	//#endregion

	//#region getLastAccessedTime()
	/**
	 * 覆盖方法
	 * @author 刘虻
	 * 2008-7-8下午06:22:31
	 */
	@Override
    public long getLastAccessedTime() {
		return lastAccessTime;
	}
	//#endregion

	//#region setMaxInactiveInterval(interval)
	/**
	 * 覆盖方法
	 * @author 刘虻
	 * 2008-7-8下午06:22:34
	 */
	@Override
    public void setMaxInactiveInterval(int interval) {
		if(baseFilter.isTokenMode()) {
			// token 模式不需要设置会话超时信息
			return;
		}
		inactiveInterval = interval;
	}
	//#endregion

	//#region getMaxInactiveInterval()
	/**
	 * 覆盖方法
	 * @author 刘虻
	 * 2008-7-8下午06:22:37
	 */
	@Override
    public int getMaxInactiveInterval() {
		return inactiveInterval;
	}
	//#endregion

	//#region getSessionContext()
	/**
	 * @deprecated
	 * 覆盖方法
	 * @author 刘虻
	 * 2008-7-8下午06:22:48
	 */
    public IHttpSessionContext getSessionContext() {
		return sessionContext;
	}
    //#endregion

    //#region getServletContext()
	/**
	 * 覆盖方法
	 * @author 刘虻
	 * 2008-7-8下午06:22:58
	 */
	@Override
    public IServletContext getServletContext() {
		return servletContext;
	}
	//#endregion

	//#region getAttribute(name)
	/**
	 * 覆盖方法
	 * @author 刘虻
	 * 2008-7-8下午06:23:05
	 */
	@SuppressWarnings("unchecked")
	@Override
    public Object getAttribute(String name) throws IllegalStateException {
		
		//#region 如果整个系统采用token模式
		if(baseFilter!=null && baseFilter.isTokenMode()) {
			
			// token 的 key
			String tokenVal = serveConn.getHeader(baseFilter.getTokenKey());
			if(tokenVal==null || tokenVal.length()<1) {
				tokenVal = (String)serveConn.getResponseHeader(baseFilter.getTokenKey());
				if(tokenVal==null || tokenVal.length()<1) {
					return null;
				}
			}
			
			// 拼装入参对象
			Map<String,Object> paraMap = new HashMap<>();
			paraMap.put("key",tokenVal);        // 设置获取内存值对应的对象（用户数据）
			paraMap.put("type","0");            // 0读取，1写入 2移除
			paraMap.put("time",baseFilter.getSessionTimeOut()); // 设置超时时间

			Map<String,Object> map = null; // 用户会话容器
			try {
				// 调用 内存数据容器 脚本，获取该token对应的用户容器
				map = (Map<String,Object>)baseFilter.getScriptLoader().invokeScript(this,"N100085",paraMap);
			}catch(Exception e) {
				e.printStackTrace();
			}
			if(map==null) {
				//baseFilter.getLog().log("---session getAttribute tokenVal:["+tokenVal+"] key:["+name+"] map is null");
				return null;
			}
			// 获取返回值
			Object res = map.get(name);
			//baseFilter.getLog().log("---session getAttribute tokenVal:["+tokenVal+"] key:["+name+"] mapSize:["+map.size()+"] res:["+res+"]");
			return res;
		}
		//#endregion
		
		//baseFilter.getLog().log("---session getAttribute id:["+getId()+"] key:["+name+"]");
		
		if (expired) {
			throw new IllegalStateException();
		}
		return get(name);
	}
	//#endregion

	//#region getValue(name)
	/**
	 * 覆盖方法
	 * @author 刘虻
	 * 2008-7-8下午06:23:33
	 */
    public Object getValue(String name) throws IllegalStateException {
		return getAttribute(name);
	}
    //#endregion

    //#region getAttributeNames()
	/**
	 * 覆盖方法
	 * @author 刘虻
	 * 2008-7-8下午06:23:44
	 */
	@SuppressWarnings({ "unchecked", "rawtypes" })
	@Override
    public Enumeration<String> getAttributeNames() throws IllegalStateException {
		
		if(baseFilter!=null && baseFilter.isTokenMode()) {
			// 获取用户的会话容器
			Map<String,Object> map = getAttributeMap();
			if(map!=null) {
				// 获取主键迭代类
				Iterator<String> iterator = map.keySet().iterator();
				
				// 返回枚举类实例
				return new Enumeration() {
					
					@Override
					public String nextElement() {
						return iterator.next();
					}
					
					@Override
					public boolean hasMoreElements() {
						return iterator.hasNext();
					}
				};
			}
			return  new Enumeration() {
				
				@Override
				public String nextElement() {
					return null;
				}
				
				@Override
				public boolean hasMoreElements() {
					return false;
				}
			};
		}
		if (expired) {
			throw new IllegalStateException();
		}
		return keys();
	}
	//#endregion

	//#region getValueNames()
	/**
	 * 覆盖方法
	 * @author 刘虻
	 * 2008-7-8下午06:23:59
	 */
    public java.lang.String[] getValueNames() throws IllegalStateException {
		Enumeration<String> e = getAttributeNames();
		Vector<String> names = new Vector<String>();
		while (e.hasMoreElements()) {
			names.addElement(e.nextElement());
		}
		String[] result = new String[names.size()];
		names.copyInto(result);
		return result;
	}
    //#endregion

    //#region setAttribute(name,value)
	/**
	 * 覆盖方法
	 * @author 刘虻
	 * 2008-7-8下午06:24:18
	 */
	@SuppressWarnings("unchecked")
	@Override
    public void setAttribute(String name, Object value) throws IllegalStateException {
		
		//#region 如果整个系统采用token模式
		if(baseFilter!=null && baseFilter.isTokenMode()) {

			// token 的 key
			String tokenVal = serveConn.getHeader(baseFilter.getTokenKey());
			if(tokenVal==null || tokenVal.length()<1) {
				tokenVal = (String)serveConn.getResponseHeader(baseFilter.getTokenKey());
				if(tokenVal==null || tokenVal.length()<1) {
					tokenVal = baseFilter.generateTokenId();
					serveConn.setHeader(baseFilter.getTokenKey(),tokenVal);
				}
			}
			
			//baseFilter.getLog().log("---session setAttribute tokenVal:["+tokenVal+"] key:["+name+"] value:["+value+"]");
			
			// 拼装入参对象
			Map<String,Object> paraMap = new HashMap<>();
			paraMap.put("key",tokenVal);        // 设置获取内存值对应的对象（用户数据）
			paraMap.put("type","0");            // 0读取，1写入 2移除
			paraMap.put("time",baseFilter.getSessionTimeOut()); // 设置超时时间

			Map<String,Object> map = null; // 用户会话容器
			try {
				// 调用 内存数据容器 脚本，获取该token对应的用户容器
				map = (Map<String,Object>)baseFilter.getScriptLoader().invokeScript(this,"N100085",paraMap);
			}catch(Exception e) {
				e.printStackTrace();
			}
		
			if(map==null) {
				map = new HashMap<>();
				
				paraMap.put("type","1");            // 0读取，1写入 2移除
				paraMap.put("value",map);           // 需要写入的值
				paraMap.put("time",baseFilter.getSessionTimeOut()); // 设置超时时间
				
				try {
					// 执行存入内存
					baseFilter.getScriptLoader().invokeScript(this,"N100085",paraMap);
				}catch(Exception e) {
					e.printStackTrace();
				}
			}

			map.put(name,value);
			
			return;
		}
		//#endregion
		
		//baseFilter.getLog().log("---session setAttribute id:["+getId()+"] key:["+name+"] value:["+value+"]");
		
		if (expired) {
			throw new IllegalStateException();
		}
		//获取旧的属性值
		if(value!=null) {
			put(name,value);
		}else {
			remove(name);
		}
	}
	//#endregion

	//#region putValue(name,value)
	/**
	 * 覆盖方法
	 * @author 刘虻
	 * 2008-7-8下午06:26:42
	 */
    public void putValue(String name, Object value) throws IllegalStateException {
		setAttribute(name, value);
	}
    //#endregion

    //#region removeAttribute(name)
	/**
	 * 覆盖方法
	 * @author 刘虻
	 * 2008-7-8下午06:26:47
	 */
	@SuppressWarnings("unchecked")
	@Override
    public void removeAttribute(String name) throws IllegalStateException {
		
		//#region 如果整个系统采用token模式
		if(baseFilter!=null && baseFilter.isTokenMode()) {
			// token 的 key
			String tokenVal = serveConn.getHeader(baseFilter.getTokenKey());
			if(tokenVal==null || tokenVal.length()<1) {
				tokenVal = (String)serveConn.getResponseHeader(baseFilter.getTokenKey());
				if(tokenVal==null || tokenVal.length()<1) {
					return;
				}
			}
			
			baseFilter.getLog().log("---session removeAttribute tokenVal:["+tokenVal+"] key:["+name+"]");
			
			// 拼装入参对象
			Map<String,Object> paraMap = new HashMap<>();
			paraMap.put("key",tokenVal);        // 设置获取内存值对应的对象（用户数据）
			paraMap.put("type","0");            // 0读取，1写入 2移除
			paraMap.put("time",baseFilter.getSessionTimeOut()); // 设置超时时间

			Map<String,Object> map = null; // 用户会话容器
			try {
				// 调用 内存数据容器 脚本，获取该token对应的用户容器
				map = (Map<String,Object>)baseFilter.getScriptLoader().invokeScript(this,"N100085",paraMap);
			}catch(Exception e) {
				e.printStackTrace();
			}
			if(map==null) {
				return;
			}
			map.remove(name);
			return;
		}
		//#endregion
		
		baseFilter.getLog().log("---session removeAttribute id:["+getId()+"] key:["+name+"]");
		
		if (expired) {
            throw new IllegalStateException();
        }
		remove(name);
	}
	//#endregion

	//#region removeValue(name)
	/**
	 * 覆盖方法
	 * @author 刘虻
	 * 2008-7-8下午06:26:52
	 */
    public void removeValue(String name) throws IllegalStateException {
		removeAttribute(name);
	}
    //#endregion
    
    //#region invalidate()
	/**
	 * 覆盖方法
	 * @author 刘虻
	 * 2006-9-15上午09:07:01
	 */
	@Override
    public synchronized void invalidate() throws IllegalStateException {
		
		//#region 如果整个系统采用token模式
		if(baseFilter!=null && baseFilter.isTokenMode()) {

			// token 的 key
			String tokenVal = serveConn.getHeader(baseFilter.getTokenKey());
			if(tokenVal==null || tokenVal.length()<1) {
				tokenVal = (String)serveConn.getResponseHeader(baseFilter.getTokenKey());
				if(tokenVal==null || tokenVal.length()<1) {
					return;
				}
			}
			
			baseFilter.getLog().log("---session invalidate:["+tokenVal+"]");
			
			// 拼装入参对象
			Map<String,Object> paraMap = new HashMap<>();
			paraMap.put("key",tokenVal);        // 设置获取内存值对应的对象（用户数据）
			paraMap.put("type","2");            // 0读取，1写入 2移除

			// 调用 内存数据容器 脚本，获取该token对应的用户容器
			try {
				baseFilter.getScriptLoader().invokeScript(this,"N100085",paraMap);
			}catch(Exception e) {
				e.printStackTrace();
			}
			return;
		}
		//#endregion
		
		baseFilter.getLog().log("---session invalidate:["+getId()+"]");
		
		if (expired) {
			throw new IllegalStateException();
		}
		Enumeration<String> enumeration = getAttributeNames();
		while (enumeration.hasMoreElements()) {
			removeAttribute(enumeration.nextElement());
		}
		setExpired(true);
	}
	//#endregion
	
	//#region isNew()
	/**
	 * 覆盖方法
	 * @author 刘虻
	 * 2006-9-15上午09:07:51
	 */
	@Override
    public boolean isNew() throws IllegalStateException {
		if (expired) {
			throw new IllegalStateException();
		}
		return lastAccessTime == 0;
	}
	//#endregion

	//#region setServletContext(sc)
	/**
	 * 设置Servlet上下文
	 * @author 刘虻
	 * 2008-7-8下午06:28:07
	 * @param sc Servlet上下文
	 */
	public synchronized void setServletContext(IServletContext sc) {
		servletContext = sc;
	}
	//#endregion

	//#region setExpired(expired)
	/**
	 * 母鸡
	 * @author 刘虻
	 * 2008-7-8下午06:29:19
	 * @param expired 母鸡
	 */
	protected void setExpired(boolean expired) {
		this.expired = expired;
	}
	//#endregion

	//#region isValid()
	/**
	 * 母鸡
	 * @author 刘虻
	 * 2008-7-8下午06:29:31
	 * @return 母鸡
	 */
	public boolean isValid() {
		return !expired;
	}
	//#endregion

	//#region checkExpired()
	/**
	 * 是否会话超时
	 * @author 刘虻
	 * 2008-7-8下午06:29:40
	 * @return true超时
	 */
	public boolean checkExpired() {
		if(expired) {
			return true;
		}
		expired = inactiveInterval > 0 && (inactiveInterval * 1000 < System.currentTimeMillis() - lastAccessTime);
		return expired;
	}
	//#endregion

	//#region userTouch()
	/**
	 * 母鸡
	 * @author 刘虻
	 * 2008-7-8下午06:29:44
	 */
	public void userTouch() {
		if (isValid()) {
            lastAccessTime = System.currentTimeMillis();
        } else {
            throw new IllegalStateException();
        }
	}
	//#endregion

	//#region save(w)
	/**
	 * 母鸡
	 * @author 刘虻
	 * 2008-7-8下午06:29:56
	 * @param w 母鸡
	 * @throws IOException 执行发生异常
	 */
	public void save(Writer w) throws IOException {
		if (expired) {
            return;
        }
		w.write(id);
		w.write(':');
		w.write(Integer.toString(inactiveInterval));
		w.write(':');
		w.write(servletContext == null || servletContext.getServletContextName() == null ? "" : servletContext.getServletContextName());
		w.write(':');
		w.write(Long.toString(lastAccessTime));
		w.write("\r\n");
		Enumeration<String> e = getAttributeNames();
		ByteArrayOutputStream os = new ByteArrayOutputStream(1024 * 16);
		while (e.hasMoreElements()) {
			String aname = e.nextElement();
			Object so = get(aname);
			if (so instanceof Serializable) {
				os.reset();
				ObjectOutputStream oos = new ObjectOutputStream(os);
				try {
					oos.writeObject(so);
					w.write(aname);
					w.write(":");
					w.write(Base64.base64Encode(os.toByteArray()));
					w.write("\r\n");
				} catch (IOException ioe) {
					ioe.printStackTrace();
					System.out.println("Problem storing session value " + aname);
				}
			}
		}
		w.write("$$\r\n");
	}
	//#endregion

	//#region restore(r,inactiveInterval,servletContext,sessionContext,serveConn,baseFilter)
	/**
	 * 母鸡
	 * @author 刘虻
	 * 2008-7-8下午06:30:16
	 * @param r                母鸡
	 * @param inactiveInterval 母鸡
	 * @param servletContext   母鸡
	 * @param sessionContext   母鸡
	 * @param serveConn        交互通信类
	 * @param baseFilter       主过滤器
	 * @return 母鸡
	 * @throws IOException 执行发生异常
	 */
	protected static SessionImpl restore(
			  BufferedReader      r
			, int                 inactiveInterval
			, IServletContext     servletContext
			, IHttpSessionContext sessionContext
			, ServeConnection     serveConn
			, BaseFilter          baseFilter) throws IOException {
		String s = r.readLine();
		if (s == null) {
			return null;
		}
		int cp = s.indexOf(':');
		if (cp < 0) {
			throw new IOException("Invalid format for a session header, no session id: " + s);
		}
		String id = s.substring(0, cp);
		int cp2 = s.indexOf(':', cp + 1);
		if (cp2 < 0) {
			throw new IOException("Invalid format for a session header, no latency: " + s);
		}
		try {
			inactiveInterval = Integer.parseInt(s.substring(cp + 1, cp2));
		} catch (NumberFormatException nfe) {
			servletContext.log("Session latency invalid:" + s.substring(cp + 1, cp2) + " " + nfe);
		}
		cp = s.indexOf(':', cp2 + 1);
		if (cp < 0) {
			throw new IOException("Invalid format for a session header, context name: " + s);
		}
		String contextName = s.substring(cp2 + 1, cp);
		SessionImpl result = new SessionImpl(
				  id
				, inactiveInterval, contextName.length() == 0 ? servletContext : null
			    , sessionContext
			    , serveConn
			    , baseFilter);
		try {
			result.lastAccessTime = Long.parseLong(s.substring(cp + 1));
		} catch (NumberFormatException nfe) {
			servletContext.log("Last access time invalid:" + s.substring(cp + 1) + " " + nfe);
		}
		do {
			s = r.readLine();
			if (s == null) {
				throw new IOException("Unexpected end of stream.");
			}
			if ("$$".equals(s)) {
				return result;
			}
			cp = s.indexOf(':');
			if (cp < 0) {
				throw new IOException("Invalid format for a session entry: " + s);
			}
			String aname = s.substring(0, cp);
			ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(Base64.decode64(s
					.substring(cp + 1))));
			try {
				result.put(aname, ois.readObject());
			} catch (ClassNotFoundException cnfe) {
				servletContext.log("Can't restore :" + aname + ", " + cnfe);
			} catch (IOException ioe) {
				servletContext.log("Can't restore :" + aname + ", " + ioe);
			}
		} while (true);
	}
	//#endregion

	//#region getAttributeMap()
	/**
	 * 覆盖方法
	 */
	@SuppressWarnings("unchecked")
	@Override
	public Map<String, Object> getAttributeMap() {
		
		//#region 如果整个系统采用token模式
		if(baseFilter!=null && baseFilter.isTokenMode()) {
			// token 的 key
			String tokenVal = serveConn.getHeader(baseFilter.getTokenKey());
			if(tokenVal==null || tokenVal.length()<1) {
				tokenVal = (String)serveConn.getResponseHeader(baseFilter.getTokenKey());
				if(tokenVal==null || tokenVal.length()<1) {
					return null;
				}
			}
			
			//baseFilter.getLog().log("---session getAttributeMap tokenVal:["+tokenVal+"]");
			
			// 拼装入参对象
			Map<String,Object> paraMap = new HashMap<>();
			paraMap.put("key",tokenVal);        // 设置获取内存值对应的对象（用户数据）
			paraMap.put("type","0");            // 0读取，1写入 2移除
			paraMap.put("time",baseFilter.getSessionTimeOut()); // 设置超时时间

			try {
				// 调用 内存数据容器 脚本，获取该token对应的用户容器
				return (Map<String,Object>)baseFilter.getScriptLoader().invokeScript(this,"N100085",paraMap);
			}catch(Exception e) {
				e.printStackTrace();
			}
		}
		//#endregion
		
		//baseFilter.getLog().log("---session getAttributeMap id:["+getId()+"]");
		
		return this;
	}
	//#endregion
}












